/*
 * Copyright (c) 2025 Industrial Software Feature Database
 */
package com.comac.ins.isfd.service.impl;

import com.comac.ins.common.core.exception.base.BaseException;
import com.comac.ins.common.core.utils.DateUtils;
import com.comac.ins.common.core.utils.StringUtils;
import com.comac.ins.common.core.utils.file.FileUtils;
import com.comac.ins.common.mybatis.core.page.PageQuery;
import com.comac.ins.common.mybatis.core.page.TableDataInfo;
import com.comac.ins.common.oss.constant.OssConstant;
import com.comac.ins.common.oss.core.OssClient;
import com.comac.ins.common.oss.entity.UploadResult;
import com.comac.ins.common.oss.enumd.AccessPolicyType;
import com.comac.ins.common.oss.factory.OssFactory;
import com.comac.ins.isfd.constant.IsfdMeshModelConstants;
import com.comac.ins.isfd.constant.enums.IsfdMongoQueryOperatorEnum;
import com.comac.ins.isfd.constant.enums.PartFileSizeUnitEnum;
import com.comac.ins.isfd.domain.IsfdDatabaseMongoQuery;
import com.comac.ins.isfd.domain.bo.IsfdMeshModelBatchDownLoadBo;
import com.comac.ins.isfd.domain.bo.IsfdMeshModelSearchBo;
import com.comac.ins.isfd.domain.bo.IsfdMeshQueryBo;
import com.comac.ins.isfd.domain.vo.IsfdDownLoadVo;
import com.comac.ins.isfd.domain.vo.IsfdMeshModelSearchShowVo;
import com.comac.ins.isfd.domain.vo.IsfdMeshModelStructureDefinitionTreeNodeVo;
import com.comac.ins.isfd.domain.vo.IsfdModelManagementRenderingResultVo;
import com.comac.ins.isfd.rpc.xuelang.XueLangRpcUtils;
import com.comac.ins.isfd.rpc.xuelang.bo.XueLangRenderBo;
import com.comac.ins.isfd.rpc.xuelang.bo.XueLangRenderExtraBo;
import com.comac.ins.isfd.rpc.xuelang.constants.enums.XueLangRenderFileTypeEnum;
import com.comac.ins.isfd.service.IIsfdCollectBatchHistoryService;
import com.comac.ins.isfd.service.IIsfdDatabaseMongoService;
import com.comac.ins.isfd.service.IIsfdMeshModelService;
import com.comac.ins.isfd.service.IIsfdMeshModelStructureDefinitionService;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.BDFEntity;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.BDFEntityShowVo;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.cards.BaseCard;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.cards.BaseCardVo;
import com.comac.ins.isfd.util.bdfparser.entities.bdf.rawdata.RawDataLine;
import com.comac.ins.isfd.util.bdfparser.enums.ContentTypeEnum;
import com.comac.ins.isfd.util.bdfparser.enums.DeckTypeEnum;
import com.comac.ins.isfd.util.bdfparser.parsers.BDFFileUtils;
import com.comac.ins.isfd.util.bdfparser.parsers.BDFParser;
import com.comac.ins.system.service.ISysDictDataService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 自然网格模型service业务处理
 *
 * @author lj
 * @date 2025-01-15
 */
@RequiredArgsConstructor
@Service
@Slf4j
public class IsfdMeshModelServiceImpl implements IIsfdMeshModelService {
    @Autowired
    private IIsfdMeshModelStructureDefinitionService iIsfdMeshModelStructureDefinitionService;

    @Autowired
    private IIsfdCollectBatchHistoryService iIsfdCollectBatchHistoryService;

    @Autowired
    private IIsfdDatabaseMongoService mongoService;

    @Autowired
    private ISysDictDataService iSysDictDataService;

    @Autowired
    private XueLangRpcUtils xueLangRpcUtils;

    @Value("${evn.evnType}")
    private String evnType;

    @Override
    public List<IsfdMeshModelStructureDefinitionTreeNodeVo> tree(String sectionName) {
        // 查询到有哪些机型
        List<IsfdMeshModelStructureDefinitionTreeNodeVo> isfdMeshModelStructureDefinitionTreeNodeVoList = iIsfdMeshModelStructureDefinitionService.listTree(iIsfdCollectBatchHistoryService::batchHistoryList, Boolean.FALSE, sectionName);
        return isfdMeshModelStructureDefinitionTreeNodeVoList;
    }

    @Override
    public TableDataInfo<Document> queryList(IsfdMeshQueryBo query) {
        PageQuery pageQuery = query.getPageQuery();
        String modelNumber = query.getModelNumber();
        String sectionNumber = query.getSectionNumber();
        String batchNumber = query.getBatchNumber();
        String fileName = query.getFileName();
        Date uploadTimeStartTime = query.getUploadTimeStartTime();
        Date uploadTimeEndTime = query.getUploadTimeEndTime();
        List<IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition> conditions = new ArrayList<>();
        if (StringUtils.isNotBlank(modelNumber)) {
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationModel = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationModel.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
            operationModel.setValue(modelNumber);
            operationModel.setFieldName("modelNumber");
            conditions.add(operationModel);
        }
        if (StringUtils.isNotBlank(sectionNumber)) {
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationSection = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationSection.setOperator(IsfdMongoQueryOperatorEnum.LIKE.getValue());
            operationSection.setValue(sectionNumber);
            operationSection.setFieldName("sectionNumber");
            conditions.add(operationSection);
        }
        if (StringUtils.isNotBlank(batchNumber)) {
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationBatch = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationBatch.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
            operationBatch.setValue(batchNumber);
            operationBatch.setFieldName("batchNumber");
            conditions.add(operationBatch);
        }
        if (StringUtils.isNotBlank(fileName)) {
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationBatch = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationBatch.setOperator(IsfdMongoQueryOperatorEnum.LIKE.getValue());
            operationBatch.setValue(fileName);
            operationBatch.setFieldName("fileName");
            conditions.add(operationBatch);
        }
        if (uploadTimeStartTime != null && uploadTimeEndTime != null) {
            long uploadTimeEndTimeTime = uploadTimeEndTime.getTime();
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationBatchEnd = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationBatchEnd.setOperator(IsfdMongoQueryOperatorEnum.LESS_THAN_EQUALS.getValue());
            operationBatchEnd.setValue(String.valueOf(uploadTimeEndTimeTime));
            operationBatchEnd.setFieldName("uploadTime");
            long uploadTimeStartTimeTime = uploadTimeStartTime.getTime();
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition operationBatchBegin = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            operationBatchBegin.setOperator(IsfdMongoQueryOperatorEnum.GREATER_THAN_EQUALS.getValue());
            operationBatchBegin.setValue(String.valueOf(uploadTimeStartTimeTime));
            operationBatchBegin.setFieldName("uploadTime");
            conditions.add(operationBatchEnd);
            conditions.add(operationBatchBegin);
        }
        IsfdDatabaseMongoQuery queryCondition = new IsfdDatabaseMongoQuery();
        queryCondition.setConditions(conditions);
        queryCondition.setPageQuery(pageQuery);

        // 查询结果
        Long totalCount = mongoService.count(getMeshModelTableName(), queryCondition);
        List<Document> result = mongoService.aggregationQuery(getMeshModelTableName(), queryCondition);
        result.forEach(doc -> {
            Long uploadTime = doc.getLong("uploadTime");
            doc.put("uploadTimeStr", DateUtils.parseDateToLong(DateUtils.YYYY_MM_DD_HH_MM_SS, uploadTime));

            Integer fileSize = doc.getInteger("fileSize");
            doc.put("fileSizeWithUnit", PartFileSizeUnitEnum.getFileSizeWithUnit(Long.valueOf(fileSize)));

            // oss的url需要加签
            Object url = doc.get("url");
            Object filePath = doc.get("filePath");
            if (url != null && filePath != null) {
                String signUrl = signUrl((String) filePath, (String) url);
                doc.put("url", signUrl);
            }
        });
        return TableDataInfo.build(result, totalCount);
    }

    @Override
    public BDFEntityShowVo getBDFPreview(String id) {
        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("未找到文件！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }
        // 获取oss的url地址
        String url = document.getString("url");
        if (StringUtils.isBlank(url)) {
            url = uploadFileToSpecificPath(filePath);
            // 更新数据库中的URL
            updateDocumentUrl(id, url, filePath.toString());
        }

        BDFEntityShowVo showVo = new BDFEntityShowVo();
        BDFEntity bdfEntity = BDFParser.readBDF(filePath);
        showVo.setContentType(bdfEntity.getContentType());
        Map<String, Integer> cardCount = bdfEntity.getBulkDataEntry().getCardCount();
        List<BaseCard> cardList = bdfEntity.getBulkDataEntry().getCardList();

        if (ContentTypeEnum.INDEX.getValue().equals(bdfEntity.getContentType())) {
            List<RawDataLine> rawDataLines = bdfEntity.getParsedDataLines().get(DeckTypeEnum.FILE_MANAGEMENT_STATEMENT.getType());
            for (RawDataLine rawDataLine : rawDataLines) {
                String modelNumber = document.getString("modelNumber");
                Long uploadTime = document.getLong("uploadTime");
                List<IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition> conditions = new ArrayList<>();
                IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition modelNumberQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
                modelNumberQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
                modelNumberQueryCondition.setValue(modelNumber);
                modelNumberQueryCondition.setFieldName("modelNumber");
                IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition uploadTimeQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
                uploadTimeQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
                uploadTimeQueryCondition.setValue(String.valueOf(uploadTime));
                uploadTimeQueryCondition.setFieldName("uploadTime");
                uploadTimeQueryCondition.setValueNumberCheck(Boolean.TRUE);
                IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition fileNameQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();

                String fileName = BDFFileUtils.getIndexFileNameWithLine(rawDataLine.getLine());
                fileNameQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
                fileNameQueryCondition.setValue(fileName);
                fileNameQueryCondition.setFieldName("fileName");

                conditions.add(modelNumberQueryCondition);
                conditions.add(uploadTimeQueryCondition);
                conditions.add(fileNameQueryCondition);
                IsfdDatabaseMongoQuery queryCondition = new IsfdDatabaseMongoQuery();
                queryCondition.setConditions(conditions);
                Document isfdMeshModelDoc = mongoService.aggregationQueryUnique(getMeshModelTableName(), queryCondition);
                if (isfdMeshModelDoc != null) {
                    rawDataLine.setMeshModelId(isfdMeshModelDoc.get("_id").toString());
                }
            }
            showVo.setParsedDataLines(rawDataLines);
        } else if (ContentTypeEnum.NORMAL.getValue().equals(bdfEntity.getContentType())
            && !CollectionUtils.isEmpty(cardCount)
            && !CollectionUtils.isEmpty(cardList)) {
            showVo.setCardCount(cardCount);
            List<BaseCardVo> cardVoList = new ArrayList<>();
            for (int i = 0; i < cardList.size(); i++) {
                BaseCard baseCard = cardList.get(i);
                BaseCardVo.buildBaseCardVoList(cardVoList, baseCard);
            }
            showVo.setCardList(cardVoList);
        } else {
            showVo.setContentType(ContentTypeEnum.OTHER.getValue());
            List<RawDataLine> rawDataLines = bdfEntity.getParsedDataLines().get(DeckTypeEnum.BULK_DATA_ENTRY.getType());
            List<RawDataLine> resultList = rawDataLines.stream().filter(r -> !r.getLine().startsWith("$") && !r.getLine().trim().isEmpty()).toList();
            showVo.setParsedDataLines(resultList);
        }

        // 需要加签
        String signUrl = signUrl(filePath.toString(), url);
        showVo.setUrl(signUrl);
        return showVo;
    }

    /**
     * 上传文件到OSS指定路径
     */
    private String uploadFileToSpecificPath(Path filePath) {
        try {
            OssClient storage = OssFactory.instance(OssConstant.PREPROCESSING_CONFIG_KEY);
            // 使用新的指定路径上传方法
            UploadResult uploadResult = storage.uploadToPath(filePath, filePath.toString());
            return uploadResult.getUrl();
        } catch (Exception e) {
            log.error("文件上传OSS失败", e);
            throw new BaseException("文件上传OSS失败");
        }
    }

    /**
     * 更新文档中的URL
     */
    private void updateDocumentUrl(String id, String url, String filePath) {
        Map<String, Object> update = new HashMap<>();
        update.put("url", url);
        update.put("filePath", filePath);
        mongoService.updateById(getMeshModelTableName(), id, update);
    }

    @Override
    public IsfdMeshModelSearchShowVo searchBDFContent(IsfdMeshModelSearchBo bo) {
        String id = bo.getId();
        String cardName = bo.getCardName();
        String value = bo.getValue();
        if (!StringUtils.isNotBlank(cardName) && !StringUtils.isNotBlank(value)) {
            throw new BaseException("请输入检索内容！");
        }

        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("未找到文件！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }

        BDFEntity result = BDFParser.readSimpleBDF(filePath);
        List<Long> matchNumberList = new ArrayList<>();

        Map<String, List<RawDataLine>> parsedDataLines = result.getParsedDataLines();
        List<RawDataLine> bulkDataEntry = parsedDataLines.get("bulkDataEntry");
        if (CollectionUtils.isEmpty(bulkDataEntry)) {
            throw new BaseException("未检索到数据！");
        }
        List<RawDataLine> resultList = null;
        // 校验一下单元卡
        if (StringUtils.isNotBlank(cardName)) {
            Map<String, Integer> cardCount = result.getBulkDataEntry().getCardCount();
            if (CollectionUtils.isEmpty(cardCount)) {
                throw new BaseException("没有单元卡信息！");
            }
            if (cardCount.get(cardName) == null && cardCount.get(cardName) == 0) {
                throw new BaseException("检索的单元卡不存在！");
            }
        }

        if (StringUtils.isNotBlank(cardName) && StringUtils.isNotBlank(value)) {
            resultList = bulkDataEntry.stream().filter(r -> r.getLine().startsWith(cardName) && r.getLine().contains(value) && !r.getLine().startsWith("$")).toList();
        } else if (StringUtils.isNotBlank(cardName) && !StringUtils.isNotBlank(value)) {
            resultList = bulkDataEntry.stream().filter(r -> r.getLine().startsWith(cardName) && !r.getLine().startsWith("$")).toList();
        } else if (!StringUtils.isNotBlank(cardName) && StringUtils.isNotBlank(value)) {
            resultList = bulkDataEntry.stream().filter(r -> r.getLine().contains(value) && !r.getLine().startsWith("$")).toList();
        }
        if (CollectionUtils.isEmpty(resultList)) {
            throw new BaseException("未检索到数据！");
        }
        matchNumberList = resultList.stream().map(rawDataLine -> Long.valueOf(rawDataLine.getRowNumber())).toList();
        IsfdMeshModelSearchShowVo showVo = new IsfdMeshModelSearchShowVo();
        showVo.set_id(id);
        showVo.setMatchNumberList(matchNumberList);
        return showVo;
    }

    @Override
    public void download(String id, HttpServletResponse response) {
        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("未找到文件！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }

        FileUtils.setAttachmentResponseHeader(response, filePath.getFileName().toString());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");

        // 写入文件内容到响应
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(filePath.toFile()))) {
            response.setContentLengthLong(Files.size(filePath));
            IOUtils.copy(in, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            log.error("写出文件报错", e);
            throw new BaseException("写出文件报错");
        }
    }

    @Override
    public IsfdDownLoadVo downloadDetail(String id) {
        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("未找到文件！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }
        BDFEntity result = BDFParser.readBDF(filePath);

        IsfdDownLoadVo isfdDownLoadVo = new IsfdDownLoadVo();
        isfdDownLoadVo.setFileName(document.getString("fileName"));
        isfdDownLoadVo.setSuffix(document.getString("fileSuffix"));
        if (ContentTypeEnum.INDEX.getValue().equals(result.getContentType())) {
            isfdDownLoadVo.setSuffix("zip");
        }
        return isfdDownLoadVo;
    }


    @Override
    public void newDownload(String id, HttpServletResponse response) {
        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("未找到文件！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }
        String fileName = document.getString("fileName");
        BDFEntity result = BDFParser.readBDF(filePath);
        if (ContentTypeEnum.INDEX.getValue().equals(result.getContentType())) {
            // 如果是索引类型，递归处理所有关联文件并打包
            List<RawDataLine> rawDataLines = result.getParsedDataLines().get(DeckTypeEnum.FILE_MANAGEMENT_STATEMENT.getType());
            // 存储文件路径和层级目录
            List<Path> filesToZip = new ArrayList<>();
            Map<Path, String> fileHierarchy = new HashMap<>();

            // 把最外层的索引文件放进压缩包,最外层的文件路径是 filePath
            filesToZip.add(filePath);
            fileHierarchy.put(filePath, filePath.getFileName().toString());

            // 递归处理索引文件
            processIndexFiles(document, rawDataLines, filesToZip, fileHierarchy, fileName);

            // 创建临时压缩包
            String zipName = fileName + ".zip";
            Path zipFilePath = Paths.get(System.getProperty("java.io.tmpdir"), zipName);
            try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
                // 将文件添加到压缩包中
                for (Path file : filesToZip) {
                    String entryName = fileHierarchy.get(file);
                    ZipEntry zipEntry = new ZipEntry(entryName);
                    zipOut.putNextEntry(zipEntry);
                    Files.copy(file, zipOut);
                    zipOut.closeEntry();
                }
            } catch (IOException e) {
                log.error("创建压缩包失败", e);
                throw new BaseException("创建压缩包失败");
            }

            // 设置响应头
            FileUtils.setAttachmentResponseHeader(response, zipName);
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");

            // 将压缩包写入响应
            try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(zipFilePath.toFile()))) {
                response.setContentLengthLong(Files.size(zipFilePath));
                IOUtils.copy(in, response.getOutputStream());
                response.flushBuffer();
            } catch (Exception e) {
                log.error("写出压缩包报错", e);
                throw new BaseException("写出压缩包报错");
            } finally {
                // 删除临时压缩包
                try {
                    Files.deleteIfExists(zipFilePath);
                } catch (IOException e) {
                    log.warn("删除临时压缩包失败", e);
                }
            }
        } else {
            // 如果不是索引类型，直接返回文件
            FileUtils.setAttachmentResponseHeader(response, filePath.getFileName().toString());
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");

            // 写入文件内容到响应
            try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(filePath.toFile()))) {
                response.setContentLengthLong(Files.size(filePath));
                IOUtils.copy(in, response.getOutputStream());
                response.flushBuffer();
            } catch (Exception e) {
                log.error("写出文件报错", e);
                throw new BaseException("写出文件报错");
            }
        }
    }

    @Override
    public void batchDownload(List<String> ids, HttpServletResponse response) {
        // 存储所有需要压缩的文件路径和层级目录
        List<Path> filesToZip = new ArrayList<>();
        Map<Path, String> fileHierarchy = new HashMap<>();

        // 遍历每个 id
        for (String id : ids) {
            Document document = mongoService.findById(getMeshModelTableName(), id);
            if (document == null) {
                throw new BaseException("未找到文件，id: " + id);
            }

            Path filePath = Paths.get(document.getString("fileUrl"));
            if (!Files.exists(filePath)) {
                throw new BaseException("未找到文件，id: " + id);
            }

            BDFEntity result = BDFParser.readBDF(filePath);
            if (ContentTypeEnum.INDEX.getValue().equals(result.getContentType())) {
                // 外层索引文件放入
                filesToZip.add(filePath);
                fileHierarchy.put(filePath, filePath.getFileName().toString());
                // 如果是索引类型，递归处理所有关联文件并打包
                List<RawDataLine> rawDataLines = result.getParsedDataLines().get(DeckTypeEnum.FILE_MANAGEMENT_STATEMENT.getType());
                // 递归处理索引文件
                processIndexFiles(document, rawDataLines, filesToZip, fileHierarchy, "");
            } else {
                filesToZip.add(filePath);
                fileHierarchy.put(filePath, filePath.getFileName().toString());
            }
        }

        // 去重
        Set<Path> uniqueFiles = new HashSet<>(filesToZip);
        filesToZip.clear();
        filesToZip.addAll(uniqueFiles);

        // 创建临时压缩包
        String zipName = "batch_download_" + System.currentTimeMillis() + ".zip";
        Path zipFilePath = Paths.get(System.getProperty("java.io.tmpdir"), zipName);
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
            // 将文件添加到压缩包中
            for (Path file : filesToZip) {
                String entryName = fileHierarchy.get(file);
                ZipEntry zipEntry = new ZipEntry(entryName);
                zipOut.putNextEntry(zipEntry);
                Files.copy(file, zipOut);
                zipOut.closeEntry();
            }
        } catch (IOException e) {
            log.error("创建压缩包失败", e);
            throw new BaseException("创建压缩包失败");
        }

        // 设置响应头
        FileUtils.setAttachmentResponseHeader(response, zipName);
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");

        // 将压缩包写入响应
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(zipFilePath.toFile()))) {
            response.setContentLengthLong(Files.size(zipFilePath));
            IOUtils.copy(in, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            log.error("写出压缩包报错", e);
            throw new BaseException("写出压缩包报错");
        } finally {
            // 删除临时压缩包
            try {
                Files.deleteIfExists(zipFilePath);
            } catch (IOException e) {
                log.warn("删除临时压缩包失败", e);
            }
        }
    }

    @Override
    public void batchDownloadWithName(IsfdMeshModelBatchDownLoadBo isfdMeshModelBatchDownLoadBo, HttpServletResponse response) {
        String zipName = isfdMeshModelBatchDownLoadBo.getZipName();
        List<String> ids = isfdMeshModelBatchDownLoadBo.getIds();
        // 存储所有需要压缩的文件路径和层级目录
        List<Path> filesToZip = new ArrayList<>();
        Map<Path, String> fileHierarchy = new HashMap<>();
        List<String> indexFileContent = new ArrayList<>(); // 用于存储索引文件内容

        // 遍历每个 id
        for (String id : ids) {
            Document document = mongoService.findById(getMeshModelTableName(), id);
            if (document == null) {
                throw new BaseException("未找到文件，id: " + id);
            }

            Path filePath = Paths.get(document.getString("fileUrl"));
            if (!Files.exists(filePath)) {
                throw new BaseException("未找到文件，id: " + id);
            }

            BDFEntity result = BDFParser.readBDF(filePath);
            if (ContentTypeEnum.INDEX.getValue().equals(result.getContentType())) {
                // 外层索引文件放入
                filesToZip.add(filePath);
                fileHierarchy.put(filePath, filePath.getFileName().toString());
                // 如果是索引类型，递归处理所有关联文件并打包
                List<RawDataLine> rawDataLines = result.getParsedDataLines().get(DeckTypeEnum.FILE_MANAGEMENT_STATEMENT.getType());
                // 递归处理索引文件
                processIndexFiles(document, rawDataLines, filesToZip, fileHierarchy, "");
            } else {
                filesToZip.add(filePath);
                fileHierarchy.put(filePath, filePath.getFileName().toString());
            }

            // 将文件名添加到索引文件内容中
            indexFileContent.add("INCLUDE '" + filePath.getFileName().toString() + "'");
        }

        // 去重
        Set<Path> uniqueFiles = new HashSet<>(filesToZip);
        filesToZip.clear();
        filesToZip.addAll(uniqueFiles);

        // 创建临时压缩包
        String zipFileName = zipName + ".zip";
        Path zipFilePath = Paths.get(System.getProperty("java.io.tmpdir"), zipFileName);
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
            // 将索引文件写入压缩包
            ZipEntry indexEntry = new ZipEntry(zipName + ".bdf");
            zipOut.putNextEntry(indexEntry);
            for (String line : indexFileContent) {
                zipOut.write((line + "\n").getBytes(StandardCharsets.UTF_8));
            }
            zipOut.closeEntry();

            // 将文件添加到压缩包中
            for (Path file : filesToZip) {
                String entryName = fileHierarchy.get(file);
                ZipEntry zipEntry = new ZipEntry(entryName);
                zipOut.putNextEntry(zipEntry);
                Files.copy(file, zipOut);
                zipOut.closeEntry();
            }
        } catch (IOException e) {
            log.error("创建压缩包失败", e);
            throw new BaseException("创建压缩包失败");
        }

        // 设置响应头
        FileUtils.setAttachmentResponseHeader(response, zipFileName);
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");

        // 将压缩包写入响应
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(zipFilePath.toFile()))) {
            response.setContentLengthLong(Files.size(zipFilePath));
            IOUtils.copy(in, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            log.error("写出压缩包报错", e);
            throw new BaseException("写出压缩包报错");
        } finally {
            // 删除临时压缩包
            try {
                Files.deleteIfExists(zipFilePath);
            } catch (IOException e) {
                log.warn("删除临时压缩包失败", e);
            }
        }
    }

    @Override
    public Boolean deleteWithValidById(String id) {
        String tableName = getMeshModelTableName();
        mongoService.deleteByIds(tableName, Collections.singletonList(String.valueOf(id)));
        return Boolean.TRUE;
    }

    @Override
    public Boolean reUpload(MultipartFile file, String id) {
        Document isfdMeshModelDoc = mongoService.findById(getMeshModelTableName(), id);
        if (isfdMeshModelDoc == null) {
            throw new BaseException("未查到数据！");
        }
        String fileSuffix = isfdMeshModelDoc.getString("fileSuffix");

        String uploadedFileName = file.getOriginalFilename();
        String uploadedFileSuffix = uploadedFileName.substring(uploadedFileName.lastIndexOf('.') + 1);

        // 判断文件类型后缀是否相同
        if (!uploadedFileSuffix.equals(fileSuffix)) {
            throw new BaseException("上传的文件名后缀与现有文件不匹配！");
        }

        // 把上传的文件根据fileUrl，进行文件替换
        try {
            Path fileUrl = Paths.get(isfdMeshModelDoc.getString("fileUrl"));
            Files.deleteIfExists(fileUrl);
            file.transferTo(fileUrl.toFile());
        } catch (IOException e) {
            throw new BaseException("文件替换失败: " + e.getMessage());
        }
        return Boolean.TRUE;
    }

    @Override
    public IsfdModelManagementRenderingResultVo loadModel(String id) {
        Document document = mongoService.findById(getMeshModelTableName(), id);
        if (document == null) {
            throw new BaseException("自然网格模型库不存在，请确认后重试！");
        }
        Path filePath = Paths.get(document.getString("fileUrl"));
        // 确保文件存在
        if (!Files.exists(filePath)) {
            throw new BaseException("未找到文件！");
        }
        Object urlObject = document.get("url");
        String url = null;
        // 获取oss的url地址
        if (urlObject == null) {
            url = uploadFileToSpecificPath(filePath);
            // 更新数据库中的URL
            updateDocumentUrl(id, url, filePath.toString());
        } else {
            url = (String) urlObject;
        }

        String fileSuffix = document.getString("fileSuffix");
        XueLangRenderFileTypeEnum xueLangRenderFileTypeEnum = XueLangRenderFileTypeEnum.forValue(fileSuffix);
        if (xueLangRenderFileTypeEnum == null) {
            throw new BaseException("自然网格模型库文件格式不正确，请确认是否是zip或者bdf格式！");
        }
        // url加签
        String signUrl = signUrl(filePath.toString(), url);
        XueLangRenderBo xueLangRenderBo = new XueLangRenderBo();
        xueLangRenderBo.setUrl(signUrl);
        xueLangRenderBo.setType(XueLangRenderFileTypeEnum.BDF.getValue());
        if (xueLangRenderFileTypeEnum.getValue().endsWith(XueLangRenderFileTypeEnum.ZIP.getValue())) {
            xueLangRenderBo.setEntryFile("");
        }
        XueLangRenderExtraBo extraBo = new XueLangRenderExtraBo();
        extraBo.setModelNumber(document.getString("modelNumber"));
        extraBo.setSectionNumbers(document.getString("sectionNumber"));
        extraBo.setSectionNames(document.getString("sectionName"));
        extraBo.setBatchNumber(document.getString("batchNumber"));
        xueLangRenderBo.setExtra(extraBo);
        //调用第三方团队接口获取renderUrl
        String renderUrl = xueLangRpcUtils.render(xueLangRenderBo);
//        String renderUrl = "www.baidu.com";
        IsfdModelManagementRenderingResultVo result = new IsfdModelManagementRenderingResultVo();
        result.setRenderUrl(renderUrl);
        return result;
    }

    /**
     * 递归处理索引文件
     */
    private void processIndexFiles(Document document,
                                   List<RawDataLine> rawDataLines,
                                   List<Path> filesToZip,
                                   Map<Path, String> fileHierarchy,
                                   String parentDirectory) {
        for (RawDataLine rawDataLine : rawDataLines) {
            String modelNumber = document.getString("modelNumber");
            Long uploadTime = document.getLong("uploadTime");
            List<IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition> conditions = new ArrayList<>();
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition modelNumberQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            modelNumberQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
            modelNumberQueryCondition.setValue(modelNumber);
            modelNumberQueryCondition.setFieldName("modelNumber");
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition uploadTimeQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();
            uploadTimeQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
            uploadTimeQueryCondition.setValue(String.valueOf(uploadTime));
            uploadTimeQueryCondition.setFieldName("uploadTime");
            uploadTimeQueryCondition.setValueNumberCheck(Boolean.TRUE);
            IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition fileNameQueryCondition = new IsfdDatabaseMongoQuery.IsfdDatabaseMongoQueryCondition();

            String fileName = BDFFileUtils.getIndexFileNameWithLine(rawDataLine.getLine());
            fileNameQueryCondition.setOperator(IsfdMongoQueryOperatorEnum.EQUAL.getValue());
            fileNameQueryCondition.setValue(fileName);
            fileNameQueryCondition.setFieldName("fileName");

            conditions.add(modelNumberQueryCondition);
            conditions.add(uploadTimeQueryCondition);
            conditions.add(fileNameQueryCondition);
            IsfdDatabaseMongoQuery queryCondition = new IsfdDatabaseMongoQuery();
            queryCondition.setConditions(conditions);

            // 查询关联文件
            Document isfdMeshModelDoc = mongoService.aggregationQueryUnique(getMeshModelTableName(), queryCondition);
            if (isfdMeshModelDoc != null) {
                Path innerFilePath = Paths.get(isfdMeshModelDoc.getString("fileUrl"));
                String fileSuffix = isfdMeshModelDoc.getString("fileSuffix");

                // 生成文件全名：fileName + fileSuffix
                String fullFileName = fileName + "." + fileSuffix;

                // 如果是索引类型，生成目录并递归处理
                BDFEntity innerResult = BDFParser.readBDF(innerFilePath);
                if (ContentTypeEnum.INDEX.getValue().equals(innerResult.getContentType())) {
                    // 将索引文件本身添加到 COMPRESSED 包中
                    filesToZip.add(innerFilePath);
                    fileHierarchy.put(innerFilePath, StringUtils.isBlank(parentDirectory) ? fullFileName : parentDirectory + "/" + fullFileName);

                    // 生成目录路径：parentDirectory/fileName/
                    String currentDirectory = StringUtils.isBlank(parentDirectory) ? fileName : parentDirectory + "/" + fileName;

                    // 递归处理索引文件
                    List<RawDataLine> innerRawDataLines = innerResult.getParsedDataLines().get(DeckTypeEnum.FILE_MANAGEMENT_STATEMENT.getType());
                    processIndexFiles(isfdMeshModelDoc, innerRawDataLines, filesToZip, fileHierarchy, currentDirectory);
                } else {
                    // 如果不是索引类型，添加到压缩包列表
                    filesToZip.add(innerFilePath);

                    // 生成文件在 COMPRESSED 中的路径：parentDirectory/fullFileName
                    String zipEntryPath = StringUtils.isBlank(parentDirectory) ? fullFileName : parentDirectory + "/" + fullFileName;
                    fileHierarchy.put(innerFilePath, zipEntryPath);
                }
            }
        }
    }

    /**
     * 获取表名称
     */
    private String getMeshModelTableName() {
        String tableName = iSysDictDataService.selectDictValueByTypeAndLabel(IsfdMeshModelConstants.MODEL_MESH, IsfdMeshModelConstants.MESH_MODEL_TABLE_NAME);
        return StringUtils.isNotBlank(tableName) ? tableName : IsfdMeshModelConstants.DEFAULT_MESH_MODEL_TABLE_NAME;
    }

    /**
     * 对url进行验签
     */
    private String signUrl(String filePath, String url) {
        OssClient storage = OssFactory.instance(OssConstant.PREPROCESSING_CONFIG_KEY);
        if (evnType.equals("red")) {
            return storage.OssSignedUrl(filePath);
        }
        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
            return storage.getPrivateUrl(filePath, 120);
        }
        return url;
    }
}
